/*
 * Copyright 2006-2007 S W McIlroy
 * Licensed  under the  Apache License,  Version 2.0  (the "License");
 * you may not use  this file  except in  compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed  under the  License is distributed on an "AS IS" BASIS,
 * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
 * implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.mockfu;

import static org.mockfu.Exceptions.*;

import java.lang.reflect.Method;

/**
 * An <tt>{@link InvocationContext}</tt> holds the calling context of a method stub invocation,
 * including the method, object and arguments of the call.
 */
public class InvocationContext
{
    private static final ThreadLocal<InvocationContext> current = new ThreadLocal<InvocationContext>();
    private static final ThreadLocal<Boolean> incrementable = new ThreadLocal<Boolean>();

    private final Object object;
    private final Method method;
    private final Object[] args;
    private volatile Expectable expectation;

    public InvocationContext( Method method, Object object, Object[] args)
    {
        this.object = object;
        this.method = method;
        this.args = args;

        current.set( this );
    }


    void setExpectation(Expectable expectation)
    {
        this.expectation = expectation;
    }

    boolean isIncrementable()
    {
        return incrementable.get() != Boolean.FALSE;
    }

    static boolean setIncrementable(boolean value)
    {
        boolean previous = incrementable.get() != Boolean.FALSE;
        incrementable.set( value );
        return previous;
    }

    /**
     * Returns the context for the currently executing method stub. If no stub is currently
     * executing in this thread, the return value is undefined.
     *
     * @return the current method invocation context
     */
    public static InvocationContext current()
    {
        return current.get();
    }

    /**
     * Returns the object underlying the method invocation (the "this" object).
     *
     * @return the object
     */
    public Object getObject()
    {
        return object;
    }

    /**
     * Returns the method being invoked.
     *
     * @return the method
     */
    public Method getMethod()
    {
        return method;
    }

    /**
     * Returns the method invocation parameters.
     *
     * @return the method parameters
     */
    public Object[] getArguments()
    {
        return args;
    }

    /**
     * Returns the number of times the current expectation has been matched, not counting the
     * current match.
     *
     * @return number of times current expectation has matched, or -1 if no current expectation
     */
    public int getMatchCount()
    {
        Expectable localExpectation = expectation;
        return localExpectation == null ? -1 : localExpectation.getMatchCount();
    }

    /**
     * Invokes the super class implementation of this method on the same object with the same
     * arguments.
     *
     * @return the result of the super call
     */
    public Object invokeSuper()
    {
        return invokeSuper( args );
    }

    /**
     * Invokes the super class implementation of this method on the same object but with different
     * arguments.
     *
     * @param args method arguments, if any
     * @return the result of the super call
     */
    public Object invokeSuper( Object... args )
    {
        try
        {
            Class<?> klas = object.getClass();
            Method m = klas.getMethod( method.getName() + "$$Super$$", method.getParameterTypes() );
            return m.invoke( object, args );
        }
        catch( Throwable ex )
        {
            throw undeclared( ex );
        }
    }
}
